import java.io.IOException;
import java.util.Random;

import javax.swing.JFileChooser;

/**
 * 
 */

/**
 * @author Robert
 * Simulate book requests and turn-ins
 * for our Hashtable experiments
 */
public class Simulator {
	BookList books = new BookList();
	//Use a set seed so randoms the same each run
	Random rand = new Random(54342);
	//number of collisions
	int collisions;
	//number of times table regrown
	int regrows;
	//number of probes when looking something up
	int probes;
	//the number of duplicate insertions attempted
	int duplicates;
	
	/**
	 * Constructor, make the books
	 *
	 */
	public Simulator () {
        initializeBooks();
	}
	
	/**
	 * Called from constructor to initialize our booklist
	 *
	 */
	private void initializeBooks() {
		 JFileChooser chooser = new JFileChooser();
		 int res = chooser.showOpenDialog(null);
		 if (res == JFileChooser.APPROVE_OPTION) {
			 System.out.println("Opening File: " + chooser.getSelectedFile().getName());
			 try {
				books.populateBooksFromFile(chooser.getSelectedFile().getCanonicalPath());
			} catch (IOException e) {
				System.err.println("An exception occurred while initializing our book list");
				e.printStackTrace();
			}
		 }
	}
	
	/**
	 * Grab a random book (might be duplicate) to insert into
	 * the hashtable
	 * @return the random book from the table
	 */
	private Book getRandomBook() {
		int index = rand.nextInt(books.size() - 1);
		return books.getBookAt(index);
	}
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the default java string hash algorithm and use the 
	 * isbn as the key
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doOneExperiment(int size, double loadfactor, int mod) {
		duplicates = 0;
	    Hashtable<String, Book> table = new Hashtable<String, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    Book.hashtype=Book.Hashtype.DEFAULT;
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.isbn, b);
	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.isbn);
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    
	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 

	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the parameterized hash algorithm and its hash function as given in the Book class
	 * Does the experiment on the Coalesced table 
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * @param hashtype The hashtype to use
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doTwoExperiment(int size, double loadfactor, int mod, Book.Hashtype hashtype) {
		duplicates = 0;
	    Hashtable<Integer, Book> table = new Hashtable<Integer, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.hashCode(), b);
	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.hashCode() );
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    

	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod + " Hashtype: " + hashtype);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the default java string hash algorithm and use the 
	 * isbn as the key
	 * Does the experiment on the Chained table
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doOneExperimentChained(int size, double loadfactor, int mod) {
		duplicates = 0;
	    ChainingHashTable<String, Book> table = new ChainingHashTable<String, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    Book.hashtype=Book.Hashtype.DEFAULT;
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.isbn, b);
	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.isbn);
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    
	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the parameterized hash algorithm and its hash function as given in the Book class 
	 * Does the experiment on the Chained table
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * @param hashtype The hashtype to use
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doTwoExperimentChained(int size, double loadfactor, int mod, Book.Hashtype hashtype) {
		duplicates = 0;
	    ChainingHashTable<Integer, Book> table = new ChainingHashTable<Integer, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.hashCode(), b);
	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.hashCode() );
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    

	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod + " Hashtype: " + hashtype);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the default java string hash algorithm and use the 
	 * isbn as the key
	 * Does the experiment on the Coalesced table
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doOneExperimentCoalesced(int size, double loadfactor, int mod) {
		duplicates = 0;
		CoalescedHashTable<String, Book> table = new CoalescedHashTable<String, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    Book.hashtype=Book.Hashtype.DEFAULT;
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.isbn, b);
	    }
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.isbn);
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    
	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the parameterized hash algorithm and its hash function as given in the Book class
	 * Does the experiment on the Coalesced table 
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * @param hashtype The hashtype to use
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doTwoExperimentCoalesced(int size, double loadfactor, int mod, Book.Hashtype hashtype) {
		duplicates = 0;
	    CoalescedHashTable<Integer, Book> table = new CoalescedHashTable<Integer, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.hashCode(), b);
	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.hashCode() );
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    

	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod + " Hashtype: " + hashtype);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the default java string hash algorithm and use the 
	 * isbn as the key
	 * Does the experiment on the Chained table with 10 percent random removal during searching and removal
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doOneExperimentChainedRandomRemoval(int size, double loadfactor, int mod) {
		duplicates = 0;
	    ChainingHashTable<String, Book> table = new ChainingHashTable<String, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    Book.hashtype=Book.Hashtype.DEFAULT;
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.isbn, b);
	    	
	    	// remove a random book 
	    	table.remove( getRandomBook().isbn );
	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.isbn);
	    	
	    	// remove a random book 
	    	table.remove( getRandomBook().isbn);
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    
	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	
	
	/**
	 * Execute an experiment with a given set of parameters
	 * Use the parameterized hash algorithm and its hash function as given in the Book class 
	 * Does the experiment on the Chained table with 10 percent random removal during searching and removal
	 * 
	 * @param size the initial size of the hashtable to use
	 * @param loadfactor the loadfactor to maintain
	 * @param mod the regrow modifier (normally 0 or 1) to use.
	 * @param hashtype The hashtype to use
	 * 
	 * @return the total number of collisions for this experiment
	 */
	public int doTwoExperimentChainedRandomRemoval(int size, double loadfactor, int mod, Book.Hashtype hashtype) {
		duplicates = 0;
	    ChainingHashTable<Integer, Book> table = new ChainingHashTable<Integer, Book>(size, loadfactor);
	    table.setRegrowModifier(mod);
	    
	    //insert 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	duplicates += table.insert(b.hashCode(), b);
	    	
	    	// remove a random book 
	    	table.remove(getRandomBook().hashCode() );

	    }
	    
	    //lookup 1000 random books
	    for (int i = 0; i < 1000; i++) {
	    	Book b = getRandomBook();
	    	table.lookup(b.hashCode() );
	    	
	    	// remove a random book 
	    	table.remove(getRandomBook().hashCode() );
	    }
	    
	    collisions = table.collisions();
	    probes = table.probeCount();
	    regrows = table.regrowCount();
	    

	    System.out.println("*******************************");
		System.out.println("Experiment Params, Load Factor= " + loadfactor + " initial table size " + size +
				" table grow modifier " + mod + " Hashtype: " + hashtype);
		System.out.println("Collsions: " + collisions);
		System.out.println("Regrows: " + regrows);
		System.out.println("Search Probes: " + probes);
		System.out.println("Duplicate Inserts attempted: " + duplicates);
		System.out.println("*******************************");
	    return collisions;
	} 
	
	
	
	/**
	 * Main routine to run experiments and print the results for the tests 
	 * @param args Command line arguments 
	 */
	public static void main(String[] args) {
		Simulator sim = new Simulator();
		
	    System.out.println("*******************************");
	    System.out.println("EXPERIMENT ONE");
		sim.doOneExperiment(100, .5, 0);
		sim.doOneExperiment(100, .75, 0);
		sim.doOneExperiment(100, .9, 0);
		sim.doOneExperiment(89, .5, 1);
		sim.doOneExperiment(89, .75, 1);
		sim.doOneExperiment(89, .9, 1);
		sim.doTwoExperiment(89, .75, 1, Book.Hashtype.DEFAULT);
		sim.doTwoExperiment(89, .75, 1, Book.Hashtype.INTEGER);
		sim.doTwoExperiment(89, .75, 1, Book.Hashtype.USER);
		sim.doTwoExperiment(89, .75, 1, Book.Hashtype.SIMPLEADD);
		sim.doTwoExperiment(89, .75, 1, Book.Hashtype.SDBM);
		sim.doTwoExperiment(89, .75, 1, Book.Hashtype.ADLER32);
		
	    System.out.println("*******************************");
	    System.out.println("EXPERIMENT TWO - CHAINED HASH TABLE");
		sim.doOneExperimentChained(100, .5, 0);
		sim.doOneExperimentChained(100, .75, 0);
		sim.doOneExperimentChained(100, .9, 0);
		sim.doOneExperimentChained(89, .5, 1);
		sim.doOneExperimentChained(89, .75, 1);
		sim.doOneExperimentChained(89, .9, 1);
		sim.doTwoExperimentChained(89, .75, 1, Book.Hashtype.DEFAULT);
		sim.doTwoExperimentChained(89, .75, 1, Book.Hashtype.INTEGER);
		sim.doTwoExperimentChained(89, .75, 1, Book.Hashtype.USER);
		sim.doTwoExperimentChained(89, .75, 1, Book.Hashtype.SIMPLEADD);
		sim.doTwoExperimentChained(89, .75, 1, Book.Hashtype.SDBM);
		sim.doTwoExperimentChained(89, .75, 1, Book.Hashtype.ADLER32);
		
		
	    System.out.println("*******************************");
	    System.out.println("EXPERIMENT TWO - COALESCED HASH TABLE");
		sim.doOneExperimentCoalesced(100, .5, 0);
		sim.doOneExperimentCoalesced(100, .75, 0);
		sim.doOneExperimentCoalesced(100, .9, 0);
		sim.doOneExperimentCoalesced(89, .5, 1);
		sim.doOneExperimentCoalesced(89, .75, 1);
		sim.doOneExperimentCoalesced(89, .9, 1);
		sim.doTwoExperimentCoalesced(89, .75, 1, Book.Hashtype.DEFAULT);
		sim.doTwoExperimentCoalesced(89, .75, 1, Book.Hashtype.INTEGER);
		sim.doTwoExperimentCoalesced(89, .75, 1, Book.Hashtype.USER);
		sim.doTwoExperimentCoalesced(89, .75, 1, Book.Hashtype.SIMPLEADD);
		sim.doTwoExperimentCoalesced(89, .75, 1, Book.Hashtype.SDBM);
		sim.doTwoExperimentCoalesced(89, .75, 1, Book.Hashtype.ADLER32);	
		
	    System.out.println("*******************************");
	    System.out.println("EXPERIMENT TWO - RANDOM REMOVAL");
		sim.doOneExperimentChainedRandomRemoval(100, .5, 0);
		sim.doOneExperimentChainedRandomRemoval(100, .75, 0);
		sim.doOneExperimentChainedRandomRemoval(100, .9, 0);
		sim.doOneExperimentChainedRandomRemoval(89, .5, 1);
		sim.doOneExperimentChainedRandomRemoval(89, .75, 1);
		sim.doOneExperimentChainedRandomRemoval(89, .9, 1);
		sim.doTwoExperimentChainedRandomRemoval(89, .75, 1, Book.Hashtype.DEFAULT);
		sim.doTwoExperimentChainedRandomRemoval(89, .75, 1, Book.Hashtype.INTEGER);
		sim.doTwoExperimentChainedRandomRemoval(89, .75, 1, Book.Hashtype.USER);
		sim.doTwoExperimentChainedRandomRemoval(89, .75, 1, Book.Hashtype.SIMPLEADD);
		sim.doTwoExperimentChainedRandomRemoval(89, .75, 1, Book.Hashtype.SDBM);
		sim.doTwoExperimentChainedRandomRemoval(89, .75, 1, Book.Hashtype.ADLER32);
		
	}

}
